#include "ToolsMethodBuilder.h"
#include "CapabilityMethod.h"
#include <QJsonArray>
#include <QJsonValue>
#include <QDebug>

ToolsMethodBuilder::ToolsMethodBuilder()
{
    ToolsMethodBuilder::clear();
}

IMethodBuilder& ToolsMethodBuilder::setName(const QString& name)
{
    mName = name;
    return *this;
}

IMethodBuilder& ToolsMethodBuilder::setTitle(const QString& title)
{
    mTitle = title;
    return *this;
}

IMethodBuilder& ToolsMethodBuilder::setDescription(const QString& description)
{
    mDescription = description;
    return *this;
}

IMethodBuilder& ToolsMethodBuilder::setParameters(const QString& name, const QJsonObject& parameters, QString* msg)
{
    // Only accept parameter names related to tool schema
    if (name == "inputSchema" || name == "outputSchema") {
        if (name == "inputSchema") {
            mInputSchema = parameters;
        } else {
            mOutputSchema = parameters;
        }
        return *this;
    }

    /// @todo MCP protocol also support 'annotations'. Here we do not allow this parameter.
    
    if (msg != nullptr) {
        *msg += QString("Unknown parameter name: %1\n").arg(name);
    }
    return *this;
}

IMethodBuilder& ToolsMethodBuilder::setMethodTask(std::shared_ptr<Task> task)
{
    mTask = std::move(task);
    return *this;
}

CapabilityMethod ToolsMethodBuilder::build(QString* msg)
{
    if (!checkComplete()) {
        if (msg != nullptr) {
            *msg += "Required fields are not complete. Name and description must be set.\n";
        }
        return CapabilityMethod{};
    }
    
    QJsonObject method;
    method["name"] = mName;
    
    if (!mTitle.isEmpty()) {
        method["title"] = mTitle;
    }
    
    if (!mDescription.isEmpty()) {
        method["description"] = mDescription;
    }
    
    method["inputSchema"] = checkSchemaComplete(mInputSchema) ?
                mInputSchema : this->genSchema(QVector<Property>());

    if (!mOutputSchema.isEmpty() && checkSchemaComplete(mOutputSchema)) {
        method["outputSchema"] = mOutputSchema;
    }
    
    return CapabilityMethod{method, mTask};
}

void ToolsMethodBuilder::clear()
{
    mName.clear();
    mTitle.clear();
    mDescription.clear();
    mInputSchema = QJsonObject();
    mOutputSchema = QJsonObject();
    mTask.reset();
}

bool ToolsMethodBuilder::checkComplete() const
{
    /// @note inputSchema also is required, but we can generate a empty schema if it is a empty object.
    return !mName.isEmpty() && !mDescription.isEmpty() && mTask != nullptr;
}

bool ToolsMethodBuilder::checkSchemaComplete(const QJsonObject& schema) const
{
    // Schema must contain type
    if (!schema.contains("type")) {
        return false;
    }
    
    // If properties object exists and is not empty, each property must have type and description
    if (schema.contains("properties") && schema["properties"].isObject()) {
        QJsonObject properties = schema["properties"].toObject();
        if (!properties.isEmpty()) {
            for (auto it = properties.begin(); it != properties.end(); ++it) {
                if (!it.value().isObject()) {
                    return false;
                }

                QJsonObject propObj = it.value().toObject();
                if (!propObj.contains("type") || !propObj.contains("description")) {
                    return false;
                }

            }
        }
    }
    
    return true;
}

QJsonObject ToolsMethodBuilder::genSchema(const QVector<Property>& properties) const
{
    QJsonObject schema;
    schema["type"] = "object";
    
    QJsonObject props;
    QJsonArray required;
    
    for (const auto& prop : properties) {
        QJsonObject propObj;
        propObj["type"] = prop.type;
        
        if (!prop.description.isEmpty()) {
            propObj["description"] = prop.description;
        }
        
        props[prop.name] = propObj;
        
        if (prop.isRequired) {
            required.append(prop.name);
        }
    }
    
    if (!props.isEmpty()) {
        schema["properties"] = props;
    }
    
    if (!required.isEmpty()) {
        schema["required"] = required;
    }
    
    return schema;
}
